#!/usr/bin/python
# Client program
version = "0.1"
#
# MISSING FEATURES
# 
# FEA1 : Multiple servers handling
# FEA2 : md5 file identification based
# FEA3 : REGISTER checks md5 and date
# FEA5 : Defining and implementing command line option
# FEA6 : Objectify the application
# FEA7 : Support links and directory
# FEA8 : Support bandwitdh limitation
# FEA9 : Limit listening interface
# FEA10 : Configuration for log cleaner
# FEA12 : rigths handling
# FEA13 : Secure udp stream
# FEA14 : Plugable file treatment
# FEA16 : Support for small changes only such as rigths or mtime
# FEA17 : Recursively files and directories stucture
# FEA18 : alteration detection and automatic trigger transfert
#

import sys
import string
import os
import time
import md5
from datetime import datetime
from stat import *
from socket import *
from DefaultConfigParser import DefaultConfigParser

config = DefaultConfigParser()
config.read("/etc/puftp.ini")

# Set the misc parameters
host = config.get("Client", "udpAddress", '<broadcast>')
port = config.get("Client", "udpPort", 12000)
bufferSize = config.get("Data", "bufferSize", 1024)
allowBroadcast=config.get("Client", "allowUdpBroadcast", 1)
messageTimeout=config.get("Client", "messageTimeout",4) 
bannerMessage=config.get("Client", "bannerMessage", "Welcome to puftp v%s" % version)
verbose=config.get("Client", "verbose", 1)
maxResent=config.get("Client", "maxResent",3)

registered_servers=[]
ip=gethostbyaddr(gethostname())[2][0]

def uniq(alist):    # Fastest order preserving
    set = {}
    return [set.setdefault(e,e) for e in alist if e not in set]

def getNowEpoch():
	return str(time.mktime(datetime.now().timetuple())).split('.')[0]

def handleError( request ):
	if verbose: print "*** ERROR;%s;%s" % (getNowEpoch(), request)

def handleAckRegister( request ):
	try:	
		type, serverIp, serverDate, fileName, remoteStatus = request.split(';')
	except:
		print "### Error while reading request (%s)" % type
		return handleError(request)

	if remoteStatus == "KO":
		registered_servers.append(serverIp)
		if verbose: print "*** Server '%s' added in the registered server list for '%s'" % (serverIp, fileName)  
	else:	
		if verbose: print "*** Server %s : nothing to do for '%s'" %(serverIp, fileName)  
		
def handleAckCheck( request ):
	try:
		type, serverIp, serverDate, fileName, remoteStatus, missings = request.split(';', 5)
	except:
		print "### Error while reading request (%s)" % type
		return handleError( request )
	lMissings=[]
	if remoteStatus == "KO":
		for i in missings.split(';'): lMissings.append(int(i))
			
		if verbose: print "*** Server '%s' missed some data index : %s for '%s'" %(serverIp, lMissings, fileName)  
	else:	
		if verbose: print "*** Server %s is OK: nothing to do for '%s'" %(serverIp, fileName)  
	return lMissings

def handleAckCommit( request ):
	try:
		type, serverIp, serverDate, fileName, remoteStatus = request.split(';')
	except:
		print "### Error while reading request (%s)" % type
		return handleError( request )
	if remoteStatus == "KO":
		if verbose: print "*** Server '%s' failed committing file '%s' " %(serverIp, fileName)  
	else:	
		if verbose: print "*** Server %s have commit '%s' sucessfuly" %(serverIp, fileName)
		registered_servers.remove(serverIp)

def handleServerRequest( request ):

	handlers = {
		"ACK_REG": handleAckRegister,
		"ACK_CHECK": handleAckCheck,	
		"ACK_COMMIT": handleAckCommit	
		}
	
	if verbose: print "***	HANDLING '%s' request" % request.split(';', 1)[0]
	return handlers.get(request.split(';', 1)[0], handleError)(request)

addr = (host,port)

# Create socket
UDPSock = socket(AF_INET,SOCK_DGRAM)
UDPSock.setsockopt(SOL_SOCKET, SO_BROADCAST, allowBroadcast)
UDPSock.settimeout(messageTimeout)
#def_msg = bannerMessage
	
#print "\n",def_msg

localFilename=sys.argv[1]
if  not os.path.exists(localFilename): 
	print "%s doen't exists locally " % localFilename
	os._exit (1)

# registered phase
statinfo=os.stat(localFilename)
localSize=statinfo[ST_SIZE]
localLastModified=statinfo[ST_MTIME]
registeredData="REGISTER;%s;%s;%s" % (localFilename, localSize, localLastModified)

if verbose: print "--> Sending registered message '",registeredData,"'....."
if(UDPSock.sendto(registeredData, addr)):
	try:
		while (1):
			reqAck,addrServ = UDPSock.recvfrom(bufferSize)
			if reqAck:
				if verbose: print "<-- Server response received'", reqAck.strip(),"'"				
				handleServerRequest(reqAck)
					
	except :
		if verbose: print "*** No more server registration" 

if len(registered_servers)==0: 
	if verbose: print "No registered server"
	os._exit(0)

for serverIp in registered_servers:
	if verbose: print "*** %s registered as destination server for '%s'" % (serverIp, sys.argv[1])

f = open(localFilename, 'r')


nbDataReq=localSize/(bufferSize-47)+1

if verbose: print "*** %d requests needed to transfer %s(%d)" % (nbDataReq, localFilename, localSize)

listTransfertIndex=range(0, nbDataReq)
if verbose: print "*** request to send : %s" % (listTransfertIndex)

#Transfer data phase
while len(listTransfertIndex)>0 and maxResent>0:

	for i in listTransfertIndex :
		chunckDatas="DATA;%024d;%16s;" % (i, localFilename)
		if verbose: print "*** MetaData effective size : %d" % len(chunckDatas)
		metaSize= len(chunckDatas)
	
		dataSize=bufferSize-metaSize

		if verbose: print "*** reading %d bytes of data in the file. %d bytes of meta data" % (dataSize, metaSize)
		f.seek(i*dataSize)
		dataFile=f.read( dataSize )
		if verbose: print "*** %d bytes reading file '%s'" % (len(dataFile), localFilename)
		sendSize=UDPSock.sendto("%s%s" % (chunckDatas, dataFile), addr)
		
		if verbose: print "--> Sending %d bytes : %s ...." % (sendSize, chunckDatas)

	listTransfertIndex=[]
	checkData="CHECK;%s;%s;%s;%d" % (localFilename, localSize, localLastModified, nbDataReq)

	if verbose: print "--> Sending checked message '", checkData,"'....."
	if(UDPSock.sendto(checkData, addr)):
		try:
			while (1):
				reqAck,addrServ = UDPSock.recvfrom(bufferSize)
				if reqAck:
					if verbose: print "<-- Server response received'", reqAck.strip(),"'"	
					listTransfertIndex+=handleServerRequest(reqAck)

		except :
			if verbose: print "*** No more server check response" 

	listTransfertIndex=uniq(listTransfertIndex)	
	# building the missing index
	maxResent-=1
	if verbose: print "*** %d tries left" % maxResent
	if verbose: print "*** Following index missed by servers : %s" % listTransfertIndex

f.close()


# commit phase
commitData="COMMIT;%s;%s;%s" % (localFilename, localSize, localLastModified)

if verbose: print "--> Sending commit message '", commitData,"'....."
if(UDPSock.sendto( commitData, addr)):
	try:
		while (1):
			reqAck,addrServ = UDPSock.recvfrom(bufferSize)
			if reqAck:
				if verbose: print "<-- Server response received'", reqAck.strip(),"'"				
				handleServerRequest(reqAck)
					
	except :
		if verbose: print "*** No more server commit ack" 

if len(registered_servers)==0: 
	if verbose: print "*** All servers have commited '%s'" %localFilename
elif verbose: 
	print "*** %s servers have failed commiting '%s'" % (registered_servers, localFilename)


#	
# Close socket
UDPSock.close()
